// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Linq;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using NUnit.Framework;
using ValidationAspects;
using ValidationAspects.Exceptions;
using ValidationAspects.Factories;
using ValidationAspects.Sdk;
using ValidationAspects.Unity;

namespace TestValidationAspects.Unity
{
	[TestFixture]
	public class TestMarshalByRefObjectInterception
	{
		private IUnityContainer _container;

		[SetUp]
		public void SetUp()
		{
			TestHelpers.ResetFramework();

			_container = new UnityContainer();
			_container.AddNewExtension<Interception>();
		}

		#region Test Intercept Properties
		[Test]
		public void Test_InterfaceInterception_for_all_properties()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<Dummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).AddCallHandler(
				new ValidationCallHandler());

			TypeOf<Dummy>.GetProperty(x => x.PropertyB).AddValidation(new[] {new NotNullOrEmpty()});

			Dummy dummy = _container.Resolve<Dummy>();
			Test.Throws<ValidateNotNullOrEmptyException>(() => dummy.AttributedProperty = null);
			Test.Throws<ValidateNotNullOrEmptyException>(() => dummy.PropertyB = null);
		}

		[Test]
		public void Test_InterfaceInterception_for_attributed_properties_only()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<Dummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule
			                                                                                                                                                      {AttributedPropertiesOnly = true}).
				AddCallHandler(new ValidationCallHandler());

			TypeOf<Dummy>.GetProperty(x => x.PropertyB).AddValidation(new[] {new NotNullOrEmpty()});

			Dummy dummy = _container.Resolve<Dummy>();
			Test.Throws<ValidateNotNullOrEmptyException>(() => dummy.AttributedProperty = null);
			dummy.PropertyB = null;
		}
		#endregion

		#region Test Intercept Methods
		[Test]
		public void Test_InterfaceInterception_for_all_methods()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<Dummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).AddCallHandler(
				new ValidationCallHandler());

			typeof(Dummy).GetMethod("MethodB").GetParameters()[0].AddValidation(new[] {new NotNullOrEmpty()});

			Dummy dummy = _container.Resolve<Dummy>();
			Test.Throws<ValidationContextException>(() => dummy.AttributedMethod(null));
			Test.Throws<ValidationContextException>(() => dummy.MethodB(null));
		}

		[Test]
		public void Test_InterfaceInterception_for_attributed_methods_only()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<Dummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule
			                                                                                                                                                      {AttributedMethodsOnly = true}).AddCallHandler(
				new ValidationCallHandler());

			typeof(Dummy).GetMethod("MethodB").GetParameters()[0].AddValidation(new[] {new NotNullOrEmpty()});

			Dummy dummy = _container.Resolve<Dummy>();
			Test.Throws<ValidationContextException>(() => dummy.AttributedMethod(null));
			dummy.MethodB(null);
		}
		#endregion

		#region Test Generic Types
		[Test]
		public void Test_generic_class_is_object_validatable()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor(typeof(GenericDummy<>), new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).
				AddCallHandler(new ValidationCallHandler());
			GenericDummy<string> genericDummy = (GenericDummy<string>)_container.Resolve(typeof(GenericDummy<string>));

			genericDummy.DoIt("s"); // succeeds

			genericDummy.ReportObjectIsInvalid = true;

			try
			{
				genericDummy.DoIt("s");
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateObjectException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_class_is_parameter_validatable()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor(typeof(GenericDummy<>), new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).
				AddCallHandler(new ValidationCallHandler());
			GenericDummy<string> genericDummy = (GenericDummy<string>)_container.Resolve(typeof(GenericDummy<string>));

			try
			{
				genericDummy.DoIt(null);
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateNotNullOrEmptyException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_class_is_non_generic_property_validatable()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor(typeof(GenericDummy<>), new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).
				AddCallHandler(new ValidationCallHandler());
			GenericDummy<string> genericDummy = (GenericDummy<string>)_container.Resolve(typeof(GenericDummy<string>));
			genericDummy.StringProperty = "s";
			Test.Throws<ValidateNotEqualException>(() => genericDummy.StringProperty = "none");
		}

		[Test]
		public void Test_generic_class_is_generic_property_validatable()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor(typeof(GenericDummy<>), new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).
				AddCallHandler(new ValidationCallHandler());
			GenericDummy<string> genericDummy = (GenericDummy<string>)_container.Resolve(typeof(GenericDummy<string>));
			genericDummy.GenericProperty = "s";
			Test.Throws<ValidateNotEqualException>(() => genericDummy.GenericProperty = "none");
		}
		#endregion

		#region Test Generic Methods
		[Test]
		public void Test_generic_method_of_non_generic_class_is_object_validated()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<GenericMethodDummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).
				AddCallHandler(new ValidationCallHandler());
			GenericMethodDummy genericMethodDummy = _container.Resolve<GenericMethodDummy>();

			genericMethodDummy.DoIt("T", "s"); // succeeds

			genericMethodDummy.ReportObjectIsInvalid = true;

			try
			{
				genericMethodDummy.DoIt("T", "s");
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateObjectException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_method_of_non_generic_class_is_non_generic_parameter_validated()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<GenericMethodDummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).
				AddCallHandler(new ValidationCallHandler());
			GenericMethodDummy genericMethodDummy = _container.Resolve<GenericMethodDummy>();

			try
			{
				genericMethodDummy.DoIt("T", null);
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateNotNullOrEmptyException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_method_of_non_generic_class_is_generic_parameter_validated()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<GenericMethodDummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).
				AddCallHandler(new ValidationCallHandler());
			GenericMethodDummy genericMethodDummy = _container.Resolve<GenericMethodDummy>();

			try
			{
				genericMethodDummy.DoIt<string>(null, "s");
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateNotNullOrEmptyException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_method_of_non_generic_class_throws_when_resolving_mismatching_validator_applied_to_generic_parameter()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<GenericMethodDummy>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(new ValidationMatchingRule()).
				AddCallHandler(new ValidationCallHandler());
			GenericMethodDummy genericMethodDummy = _container.Resolve<GenericMethodDummy>();

			Test.Throws<ValidationException>(() => genericMethodDummy.DoIt(123, "s"), string.Format("Failed to construct validator for [type:{0}] [method:DoIt] [parameter:t].", typeof(GenericMethodDummy)));
		}
		#endregion

		#region Test Validator Attribute declaration
		[Test]
		[ExpectedException(typeof(ValidationException))]
		public void Test_method_invocation_throws_for_invalid_validation_decoration_method()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<InvalidValidatorDecorationMethod>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(
				new ValidationMatchingRule()).AddCallHandler(new ValidationCallHandler());

			_container.Resolve<InvalidValidatorDecorationMethod>().DoIt();
		}

		[Test]
		[ExpectedException(typeof(ValidationException))]
		public void Test_method_invocation_throws_for_invalid_validation_decoration_parameter()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<InvalidValidatorDecorationParameter>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(
				new ValidationMatchingRule()).AddCallHandler(new ValidationCallHandler());

			_container.Resolve<InvalidValidatorDecorationParameter>().DoIt("test");
		}

		[Test]
		[ExpectedException(typeof(ValidationException))]
		public void Test_property_invocation_throws_for_invalid_validation_decoration_property()
		{
			_container.Configure<Interception>().SetDefaultInterceptorFor<InvalidValidatorDecorationProperty>(new TransparentProxyInterceptor()).AddPolicy("ValidationPolicy").AddMatchingRule(
				new ValidationMatchingRule()).AddCallHandler(new ValidationCallHandler());

			_container.Resolve<InvalidValidatorDecorationProperty>().Property = "test";
		}
		#endregion

		#region Mocks
		public class Dummy : MarshalByRefObject
		{
			#region Implementation of IDummy
			[NotNullOrEmpty]
			public string AttributedProperty { get; set; }

			public string PropertyB { get; set; }

			public void AttributedMethod([NotNullOrEmpty] string s) {}

			public void MethodB(string s) {}
			#endregion
		}

		public class GenericDummy<T> : MarshalByRefObject
		{
			public bool ReportObjectIsInvalid { get; set; }

			[ValidateObject]
			public void DoIt([NotNullOrEmpty] string s) {}

			[NotEqual("none")]
			public string StringProperty { get; set; }

			[NotEqual("none")]
			public T GenericProperty { get; set; }

			[ValidationMethod]
			public static void ValidateDummy(GenericDummy<T> genericDummy)
			{
				if (genericDummy.ReportObjectIsInvalid)
					throw new ValidationException("invalid dummy");
			}
		}

		public class GenericMethodDummy : MarshalByRefObject
		{
			public bool ReportObjectIsInvalid { get; set; }

			[ValidateObject]
			public void DoIt<T>([NotNullOrEmpty] T t, [NotNullOrEmpty] string s) {}

			[ValidationMethod]
			public static void ValidateDummy(GenericMethodDummy genericMethodDummy)
			{
				if (genericMethodDummy.ReportObjectIsInvalid)
					throw new ValidationException("invalid dummy");
			}
		}

		[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
		public class SpecificTypeValidatorAttribute : ValidatorAttribute
		{
			#region Overrides of ValidatorAttribute
			public override IValidatorFactory Factory
			{
				get { return new InlineValidatorFactory<Dummy>((v, c) => { }); }
			}
			#endregion
		}

		public class InvalidValidatorDecorationMethod : MarshalByRefObject
		{
			[SpecificTypeValidator]
			public void DoIt() {}
		}

		public class InvalidValidatorDecorationParameter : MarshalByRefObject
		{
			public void DoIt([LessThan(100)] string s) {}
		}

		public class InvalidValidatorDecorationProperty : MarshalByRefObject
		{
			[NotEqual(123.456)]
			public string Property { get; set; }
		}
		#endregion
	}
}